#ifndef SIMPLE_SUPPORT_MISC_CPP
#define SIMPLE_SUPPORT_MISC_CPP

#include <type_traits>
#include <limits>
#include <stdexcept>
#include <string>
#include <optional>
#include <cinttypes>
#include <variant>
#include <cctype>

#include "range.hpp"
#include "type_traits/is_template_instance.hpp"
#include "type_traits/can_deref.hpp"
#include "algorithm/traits.hpp"
#include "algorithm/utils.hpp"

namespace simple::support
{

	// the simplest things are just crazy... absolutely crazy...
	[[nodiscard]] inline bool is_space(char ch) noexcept
	{ return std::isspace(static_cast<unsigned char>(ch)); }

	template <typename N>
	N strton(const char* str, char ** end = nullptr)
	{
		static_assert(std::is_arithmetic_v<N>, "simple::support::strton expects an arithmetic type!");
		if constexpr (std::is_same_v<float, N>)
			return std::strtof(str, end);
		else if constexpr (std::is_same_v<double, N>)
			return std::strtod(str, end);
		else if constexpr (std::is_same_v<long double, N>)
			return std::strtold(str, end);
		else if constexpr (std::is_same_v<long, N>)
			return std::strtol(str, end, 0);
		else if constexpr (std::is_same_v<long long, N>)
			return std::strtoll(str, end, 0);
		else if constexpr (std::is_same_v<unsigned long, N>)
			return std::strtoul(str, end, 0);
		else if constexpr (std::is_same_v<unsigned long long, N>)
			return std::strtoull(str, end, 0);
		else if constexpr (std::is_signed_v<N>)
		{
			auto n = std::strtoimax(str, end, 0);
			constexpr auto limit = range<N>::limit();
			if(!limit.intersects(n))
			{
				errno = ERANGE;
				return n < 0 ? limit.lower() : limit.upper();
			}
			return static_cast<N>(n);
		}
		else if constexpr (std::is_unsigned_v<N>)
		{
			// TODO: alright so for negative values, this guy wraps (and every other usnigned above)... how crazy is that? so if is small and wraps to huge value we error out, but if it's huge enough to get into limits we'd be fine... pretty crazy... will have to freakin parse the minus sign myself cause the most basic things are freakin insane in this god forsaken language -_-
			auto n = std::strtoumax(str, end, 0);
			if(std::numeric_limits<N>::max() < n)
			{
				errno = ERANGE;
				return std::numeric_limits<N>::max();
			}
			return static_cast<N>(n);
		}
	}

	template <typename N>
	N ston(const std::string& s, std::size_t * start_end_index)
	{
		static_assert(std::is_arithmetic_v<N>, "simple::support::ston expects an arithmetic type!");
		errno = 0;
		const char* start = s.c_str() + (start_end_index ? *start_end_index : 0);
		char* end;
		N result = strton<N>(start, &end);
		if(ERANGE == errno)
			throw std::out_of_range("simple::support::ston");
		auto diff = end - start;
		if(!diff)
			throw std::invalid_argument("simple::support::ston");
		if(start_end_index)
			*start_end_index += diff;
		return result;
	}

	template <typename N>
	N ston(const std::string& s, std::size_t start_index = 0)
	{
		return ston<N>(s, &start_index);
	}

	template <typename Number>
	std::optional<Number> to_(const std::string& s)
	{
		static_assert(std::is_arithmetic_v<Number>, "simple::support::to_<Number> expects an arithmetic type!");
		errno = 0;
		const char* start = s.c_str();
		char* end;
		Number result = strton<Number>(start, &end);
		if(ERANGE == errno)
			return std::nullopt;
		auto diff = end - start;
		if(!diff)
			return std::nullopt;
		return result;
	}

	template <typename To, typename From, std::enable_if_t<
		is_template_instance_v<std::variant, To> &&
		is_template_instance_v<std::variant, std::remove_cvref_t<From>>
	>* = nullptr>
	[[nodiscard]] constexpr
	To to_(From&& from) { return std::visit([](auto&& x) -> To { return std::forward<decltype(x)>(x); }, std::forward<From>(from)); }


	template <typename N>
	range<N> storn(const std::string& s, std::size_t * start_end_index)
	{
		static_assert(std::is_arithmetic_v<N>, "simple::support::storn expects an arithmetic type!");
		range<N> result{};
		std::size_t j = start_end_index ? *start_end_index : 0;
		result.lower() = ston<N>(s, &j);
		if(j == s.size())
			throw std::invalid_argument("simple::support::storn");
		auto delim = s[j];
		++j;
		result.upper() = ston<N>(s, &j);
		if(':' == delim)
			result.upper() += result.lower();
		if(start_end_index)
			*start_end_index = j;
		return result;
	}

	template <typename N>
	range<N> storn(const std::string& s, std::size_t start_index = 0)
	{
		return storn<N>(s, &start_index);
	}

	template <typename N>
	std::string to_string(range<N> r, const char& separator = '-')
	{
		using std::to_string;
		if(':' == separator)
			r.upper() -= r.lower();
		return to_string(r.lower()) + separator + to_string(r.upper());
	}

	template <typename It>
	[[deprecated("use std::string_view constructor")]]
	std::string_view make_string_view(It begin, It end)
	{
		return {begin, static_cast<std::string_view::size_type>(end - begin)};
	}

	// TODO: maybe put these two in algorithm/range_wrappers.hpp instead?

	template <typename Container,
		typename = std::enable_if_t<is_range_v<Container>>>
	class range_reference
	{
		public:
		using container_type = Container;
		using size_type = typename container_type::size_type;
		using iterator = typename container_type::iterator;
		using const_iterator = typename container_type::const_iterator;

		constexpr explicit range_reference (Container& container) :
			container(&container)
		{}

		constexpr auto begin() const
		{
			using std::begin;
			return begin(*container);
		}

		constexpr auto end() const
		{
			using std::end;
			return end(*container);
		}

		private:
		Container* container;
	};

	template <typename Size, typename Anchor>
	class offset_range
	{
		public:
		using anchor_type = Anchor;
		using offset_type = std::conditional_t<
			std::is_same_v<Size,void>,
				typename std::remove_reference_t<anchor_type>::size_type,
				Size
		>;

		constexpr offset_range() = default;

		template <typename A,
			std::enable_if_t<std::is_same_v<A,Anchor>>* = nullptr>
		constexpr explicit offset_range
		(
			A&& anchor,
			range<Size> i = range<Size>::limit()
		) :
			anchor(std::forward<A>(anchor)),
			i(i)
		{}

		constexpr auto begin() const
		{ return iter_at(i.lower()); }
		constexpr auto end() const
		{ return iter_at(i.upper()); }

		constexpr auto begin()
		{ return iter_at(i.lower()); }
		constexpr auto end()
		{ return iter_at(i.upper()); }

		private:
		Anchor anchor;
		range<offset_type> i;

		constexpr auto iter_at(offset_type index) const
		{
			using std::begin;
			using std::end;
			using std::clamp;

			auto begin_ = begin(anchor);
			auto end_ = end(anchor);

			// TODO: use anchor.size() if available
			return begin_ + clamp(index, offset_type{},
				static_cast<offset_type>(end_ - begin_)); // good cast, since container can not have negative size
		}

		constexpr auto iter_at(offset_type index)
		{
			using std::begin;
			using std::end;
			using std::clamp;

			auto begin_ = begin(anchor);
			auto end_ = end(anchor);

			// TODO: use anchor.size() if available
			return begin_ + clamp(index, offset_type{},
				static_cast<offset_type>(end_ - begin_)); // good cast, since container can not have negative size
		}

	};

	template <typename Anchor>
	offset_range(Anchor&&) -> offset_range<void, Anchor>;

	template <typename Size, typename Anchor>
	offset_range(Anchor&&, range<Size>) -> offset_range<Size, Anchor>;

	template <typename I>
	[[nodiscard]] auto deref(I&& i) -> decltype(auto)
	{
		if constexpr (can_deref_v<I>)
			return deref(*std::forward<I>(i));
		else
			return std::forward<I>(i);
	};

} // namespace simple::support

#endif /* end of include guard */
